All-to-All Grouping

导言

all-to-all 要让每个参与者给其他参与者发送数据,为什么还要分 group?分组可以控制同时争用有限资源的请求,让通信更接近硬件能稳定处理的速度;但它是否能“防止阻塞”,取决于分的是什么,以及阻塞发生在哪一层。本文从四个 rank 的调度例子出发,解释限流、错峰、接收许可和拓扑分层,再区分 NCCL group 的并发推进语义。目标是看懂通信实现的设计动机,并知道怎样验证收益;文中的容量与规模例子均为推导,没有集群性能实测。

先看“全部通信”要求了什么

“既然最终都要发送,越早全部发出去,应该越快。”这个判断在网络、端点和软件进度引擎都有足够资源时完全合理。分组之所以有时更快,是因为真实系统未必能同时高效处理所有请求。

先固定通信语义。设有四个 rank,编号为 0、1、2、3,每个 rank 准备四块数据:一块给自己,其余三块分别给其他 rank。例如 rank 0 的输入是 [x00, x01, x02, x03];通信完成后,rank 2 收到 [x02, x12, x22, x32]。下标的第一位是来源,第二位是目的地。

All-to-all 规定最终的数据归属,没有要求所有传输必须同时发生。这来自 MPI 对块交换的定义;具体实现可以采用直接发送、分轮交换或其他等价算法。本文讨论常见的同一通信域内交换,自身那块可以本地复制,不需要走网络。MPI All-to-All 语义

这也给“分 group”划出第一条边界:

  • 只把工作分批:四个 rank 最终仍交换全部目标数据,改变的是调度。
  • 把通信域拆成 {0,1} 和 {2,3},各自独立做 all-to-all:原来跨集合的数据没有送到,已经改变任务。只有应用本来就允许独立通信,或者另有跨组阶段补齐数据,才成立。

后文先讨论第一种:总任务不变,怎样安排它更快完成。

如果把目的概括成“防止接收端阻塞”,更精确的说法是:让接收端只接纳它能够处理的工作,把超额等待留在发送端。这通常能避免无节制注入带来的积压,但并不消灭等待,也不自动解决死锁。

针对这个目标,可以把常见设计思路分成以下四项;前两项放在同一张图中比较。这里是机制分类,不是所有通信库统一采用的算法名称。

分组方式 分的是什么 接收端得到的约束 对应图
Pairwise 轮转 按轮次选择一对发送、接收 peer 一个逻辑轮内,每个 rank 一路入向 图 1
有限并发窗口 限制当前在途请求,完成后补充 独立本地窗口不自动保证全局接收扇入;需配合收发匹配、错峰或 credit 图 1
Rank 块轮转 固定分组成员,轮转源组与目的组配对 组宽为 s 时,一个逻辑轮内每个 rank 最多面对 s 个源 图 2
拓扑分层 分开节点间与节点内等不同通信域和数据路径 能组织转发与利用快链路,但接收流量上限仍需单独设计 图 3

Credit 是可以叠加在这些方案上的流控协议,不是第五种固定拓扑。NCCL 调用 group 的含义又不同,后文单独解释。All-to-all 还有 Bruck 等面向小消息、减少通信轮次的算法,不能把所有算法设计都归因于接收拥塞。Open MPI 的算法列表与适用说明

分批为什么可能比齐发更快

活跃请求也要消耗资源

一条传输除了 payload,还可能占用请求描述符、网卡发送队列、接收缓存、软件轮询时间,以及负责搬运和处理完成事件的执行资源。具体占用哪些资源,取决于 MPI、NCCL 或自定义通信内核的实现。

因此,增加并发有两个阶段:资源尚未吃满时,它能填补等待空隙;资源已经饱和后,继续增加请求可能主要增加排队、调度开销和尾延迟。

用一个教学容量模型说明:接收端每个时间单位能处理两块数据,而这一单位突然来了六块,那么四块必须等待。提前把它们全部注入接收路径,不会把服务能力从两块提高到六块。如果额外队列又挤占共享缓存、触发回压或拖慢进度处理,总体完成时间还可能增加。反过来,如果排队没有额外代价,服务端始终满载,仅把等待从接收端搬到发送端,也不必然更快。

Open MPI 提供了可核查的工程例子。其 5.0.7 版本 Alltoall 算法既包含一次启动全部非阻塞收发的 linear,也包含只维持有限在途请求的 linear_sync;后者通过 coll_tuned_alltoall_max_requests 控制窗口。Open MPI 5.0.7 算法说明

这里的直接收益是给活跃请求数量设置上界。它不意味着 communicator 变小、已创建的 QP 被销毁,或者完整输入输出 buffer 随窗口同比缩小。能否减少内存,还取决于实现是否按窗口复用临时存储。

按 peer 轮转与窗口

假设每个 rank 一次只发一个目的地,看起来已经很克制。但如果 rank 1、2、3 都先发给 rank 0,rank 0 仍然同时收到三路流量。每个发送端的扇出小,不代表每个接收端的扇入小。多源同时汇入一个接收端,就是 incast。

错开目的地能进一步限制接收端竞争。下面是四个 rank 的循环平移调度;每个格子只写该源本轮的发送,接收由对应箭头确定。

轮次 rank 0 发送 rank 1 发送 rank 2 发送 rank 3 发送
1 0 → 1 1 → 2 2 → 3 3 → 0
2 0 → 2 1 → 3 2 → 0 3 → 1
3 0 → 3 1 → 0 2 → 1 3 → 2

每一轮,每个 rank 恰好发给一个其他 rank,也恰好从一个其他 rank 接收。三轮覆盖全部 12 条非自身有向传输;输入块保留到对应发送完成,接收块写入按来源划分的输出位置,不需要额外复制出三套完整输入。

把同一个 rank 分别画在发送列和接收列,可以直接数出每个目标有几条入边。图 1 为了避免交叉线,把右侧目的 rank 按本轮顺序重排;这不会改变物理接线。

![Peer 轮转的逻辑拓扑与受控推进流程](https://pic.shaojiemike.top/shaojiemike/2026/09/33fdd6c82cbe2d16103a36f1563b7d33.png){ width=100% }
图 1:自绘逻辑通信与流程示意。每个箭头是一条本轮允许的 rank 间数据流;两列是同一组参与者的两个角色。流程中的许可是说明真实流量上限需要的控制条件,不是声明 Open MPI 的 pairwise 内部实现了该 credit 协议。

轮转配对控制“谁同时发给谁”,窗口控制“同时保留多少工作”。如果在同一受控阶段合并 p 个不同的循环偏移,每个目标至多有 p 个源;但如果每个源独立推进自己的窗口,快源可能已经进入后续阶段,不能继续只按单个逻辑轮统计接收压力。要得到真实上限,还需防止未获准的阶段叠加,或者用接收额度直接约束在途数据。

这里的“一个逻辑轮”也不等于调用一次全局 barrier。生产实现可以用匹配收发、局部完成和 peer 许可表达必要依赖;关键是这些机制实际保护了哪项资源。

Open MPI 5.0.7 的 ompi_coll_base_alltoall_intra_pairwise 使用相应的目标计算。下面仅摘录目的 rank 计算,省略的收发 buffer 地址、sendrecv 调用和错误处理可见原文件:

1
2
sendto  = (rank + step) % size;
recvfrom = (rank + size - step) % size;

其中 rank 是当前编号,size 是通信域大小,step 是当前轮次。实现遍历 1..size,把自身复制放在最后;上表只展示非自身的三轮。固定版本源码,第 189–204 行

按 rank 块轮转

把单 rank 换成一组 rank,也能采用相同思路。例如 64 个 rank 分为四组,每组 16 个;每轮每个源组只面向一个目标组,并让组间配对形成置换。这样目标组这一轮只接收一个源组的流量,但组内每个接收 rank 仍可能面对 16 个源。组间错峰没有消除组内竞争。该例只是调度结构推导,不代表 16 是最优组宽。

为了把组内、组间两个层级画清楚,图 2 缩小成八个 rank、四组,每组两个成员。第 t 轮,源组 g 访问 (g+t) mod 4;t=0 覆盖组内交换,t=1,2,3 覆盖另外三个组,自身数据本地处理。

![Rank 块轮转拓扑与接收 credit 流程](https://pic.shaojiemike.top/shaojiemike/2026/09/e73e33837d0700a512a23944c1950b77.png){ width=100% }
图 2:自绘通用模型,上方只画第 1 个跨组阶段,下方展示可叠加的接收 credit。每个组间箭头展开为最多 2×2 条 rank 间数据流;每个接收 rank 的共享额度 C=2 是教学假设,不是 MoonEP 的硬件参数或具体信号协议。

以 G0={R0,R1} 访问 G1={R2,R3} 为例,允许的传输是 R0→R2、R0→R3、R1→R2、R1→R3。因此 R2 本轮只面对 R0 和 R1,其他源组暂时访问其他目标,或者在源端等待下一次许可。限制接收压力的原因,是本轮目标组只有一个源组进入,而不是每个组里的人彼此不通信。

一般地,P 个 rank 按每组 s 个划成 G=P/s 组,这种模型需要 G 个阶段覆盖全部组配对。其逻辑轮内扇入从全开的最多 P−1 个非自身源,降到最多 s 个;组内阶段排除自身后是 s−1。如果各阶段没有适当的推进约束,这个“每轮”上限仍不能直接当作任意时刻的在途上限。

以上保证都属于逻辑端点层面。多个不同目的地仍可能经过同一条 PCIe 上行链路、网卡或交换机出口,因此还要把 rank 编号映射回物理拓扑。网络路径选择和端点拥塞控制也有不同作用范围,不能只凭端点配对均匀就认定整网无拥塞。NVIDIA 对路径均衡与端点 incast 的区分

快慢不一致时,需要接收许可

表格把每轮画得整齐,真实 rank 的完成时间却不相同。如果快发送端直接进入下一轮,而慢接收端还在处理上一轮,原本错开的流量又可能叠到一起。

一种通用方法是 credit,接收许可:发送端只有拿到对应接收资源的许可,才发下一块;接收方在数据被消费、对应槽位能安全复用后返还许可。尚未获得许可的数据留在发送端,等待被限制在接收资源之外。

这个协议需要说清三个细节:

  1. 许可按什么计量:每条消息、每个 chunk、字节数,还是某个 group 的一轮工作;变长消息下,“两个请求”不等于固定字节数。
  2. 许可何时返还:发送方本地完成、数据对端可见、接收方消费完成,是不同事件。若许可保护接收槽位,必须等到它确实能复用。
  3. 许可覆盖谁:每个 peer 都单独允许两块,面对 32 个 peer 时仍可能积累 64 块。要限制共享接收资源,需要总额度或其他等价约束。

credit 自身增加信号、计数器和依赖;初始额度、完成通知的推进以及循环等待也必须设计正确。它是受控背压机制,不是“加上一个 flag 就绝不会挂住”的保证。

图 2 的 credit 流程可以用一个容量约束检验:设每个等长 chunk 占一个接收槽位,每个接收 rank 总共有 C 个槽位,把已获得额度但尚未发送、正在传输、已经到达但尚未释放的 chunk 分为三个互斥状态,那么它们的总数必须始终 **不超过 C**。没有额度的发送者保留源数据并等待;消费完成、槽位安全释放后才返还额度。这样保护的是接收 buffer 的容量,不是保证网络里没有排队,也不是保证消费者永远不会停顿。

若 chunk 大小不等,应改为字节额度或按最大块大小预留。若每个源各自拿到 C 个额度,而这些额度最终共用同一个只有 C 个槽位的目标 buffer,这个约束就已经被破坏了。

另一种办法是使用可补充的在途窗口。Open MPI 的 linear_sync 在有限数量的收发请求中等待任一请求完成,再补入同类型的新请求;它并不要求每批都执行全局 barrier。窗口上限分别作用于发送和接收,例如上限为四时,可以有四个发送请求加四个接收请求。这是本地请求窗口,不应直接称为接收方 credit 协议。两者都限制活跃工作,但约束的位置不同。linear_sync 源码说明及推进循环

这些机制能帮助理解仓库已有的 MoonEP Group Design:它用交互场景展示容量、错峰与许可,再映射到固定版本实现。具体的 AIV 分工、组员编号和完成协议仍应回到该文的源码证据,通用推导不能证明某个硬件上的最优参数。

“防止阻塞”需要分层理解

到这里,分组缓解拥塞的逻辑已经清楚:控制活跃工作,分散热点,并限制快发送者对慢接收者的冲击。但工程里说的“通信阻塞”,至少有以下几种不同情况。

现象 实际发生了什么 分组能帮助到哪里
拥塞与回压 请求到达速度超过链路或端点服务能力,队列增长,发送被减速或暂停 并发窗口、错峰和接收许可可能降低瞬时压力
队头阻塞 队首工作暂时不能推进,把本可执行的后续工作也挡住 需要独立队列、可绕过调度或更小粒度完成机制;仅编号分组不够
等待慢 rank 后续阶段需要的数据尚未全部到齐 流水或独立完成可以缩小等待范围;最终需要的那份慢数据仍要等
死锁 存在循环等待,所有相关操作都无法继续 必须修复依赖和提交顺序;缩小 group 本身没有保证
API 阻塞 调用尚未返回,或主机正在等待 GPU 完成 这是接口与完成语义,不能据此单独判断网络拥塞

队头阻塞尤其容易被误用。如果一个 FIFO 中,发给慢目标的数据挡住了后面发给快目标的数据,分开队列且调度器能继续服务其他队列,才有机会隔离影响。仅换一个 communicator 名字,底下仍共享同一个 FIFO,就没有自动隔离。

分组还可能增加等待:若实现规定“本组所有 peer 完成后才能开始下一组”,一个慢 peer 会挡住下一组中本可独立执行的工作。按完成事件补充窗口,可以减少这种整批等待;能否采用它,仍取决于匹配顺序、buffer 复用和应用的数据依赖。

网络层也存在这种传播:RoCE 的 PFC 按优先级暂停流量,共享该优先级的流可能一起受影响。应用层分组最多是减少触发拥塞的压力,不能直接等价成网卡队列或 PFC 优先级隔离。NVIDIA 关于 PFC 与队头阻塞的说明

NCCL group 为什么反而要合在一起

ncclGroupStart() / ncclGroupEnd() 包围一组通信调用时,group 表示把这些调用作为一批提交。它与“每轮只准少数 peer 通信”不是同一个抽象。

想象两个 rank 都在同一条各自的 CUDA stream 上先执行 send,再执行 recv。若 send 的完成依赖对方的 recv,而 recv 又排在本地尚未完成的 send 后面,就形成循环等待。这是依赖关系问题,与交换机有没有拥塞无关。

NCCL 的点对点文档明确要求:需要并发推进的调用应合入同一个 group。两端都可以按如下方式表达交换:

1
2
3
4
ncclGroupStart();
ncclSend(sendbuff, sendcount, sendtype, peer, comm, stream);
ncclRecv(recvbuff, recvcount, recvtype, peer, comm, stream);
ncclGroupEnd();

这是 NCCL 2.27 归档文档的 sendrecv 示例。peer 是对端,comm 是已有通信域,stream 是本地 CUDA stream;发送 buffer 由本 rank 产生,接收 buffer 由本 rank 分配,两端的发送与对应接收必须匹配计数和类型。group 让相关收发能够共同推进,并不创建一个更小的通信域,也不保证底层所有数据同时占用物理链路。NCCL 点对点通信文档

把等待关系看完整

分批时,不能把必须共同推进的操作切到互相等待的两个批次中。group 也不能补齐缺失的 peer、修复不匹配的消息,或消除用户在 CUDA event、stream 与其他 collective 之间构造的循环依赖。

NCCL group 还有两项相关用途:单线程管理多个 GPU 时,避免第一张卡的调用阻止其他卡的调用被提交;聚合多个操作,减少启动开销。这里的优化方向可能是合并更多调用,而限流的方向是减少同时在途的工作,两者可以在不同层次配合。NCCL Group Calls

也不能把 ncclGroupEnd() 返回理解为 GPU 通信数据已经全部完成。普通情形还需要适当的 stream 完成依赖;对非阻塞 communicator,若返回 ncclInProgress,甚至还需先确认后台提交完成,再进行相关 CUDA 操作。主机返回、提交完成和设备完成是不同边界。同一文档的 Nonblocking Group Operation

按拓扑分组还能改变数据路径

此前的分批保留同样的最终数据与直接传输关系。按节点、交换域或网络 rail 组织的分层通信,还可能改变数据经过哪里、在哪里聚合和在哪里复制。

设四台机器每台有八张 GPU。先按目标机器整理数据,跨机传送后再在机内分发,可以利用机内与机间链路的性能差异。但如果原来每个源给每个目标的内容都不同,仅把小消息拼成大消息,并不会自动减少跨机器边界必须送达的有效字节;它主要改变消息粒度、路径和资源使用,可能增加打包、转发和临时 buffer。

MoE 有一个不同的机会:同一 token 的 hidden 向量可能需要送给同一目标节点上的多个专家。假设三个专家都在那台机器上,朴素逐专家跨机发送需要三份 hidden;若路由元数据足够,跨机发送一份,到目标节点后再复制给三个专家,就能减少重复跨机 payload。这里减少的是同一份内容的跨机复制次数,不是 all-to-all 分组的一般保证。专家返回的结果通常不同,combine 是否能减少跨机字节,还需检查何处允许做局部归约。

DeepSeek-V3 技术报告 §3.2.2 给出具体实例:它限制每个 token 的目标节点数,先经 IB 发送到目标节点内相同局部编号的 GPU,再通过 NVLink 转发到持有目标专家的 GPU。报告同时描述了发送、转发和接收的 warp 分工。该证据支持路由、拓扑和通信内核共同设计的机制,不能用来证明任意分组都有同样收益,也不能直接迁移成 Ascend UDMA 的性能结论。DeepSeek-V3 Technical Report,v2,§3.2.2

图 3 用两台机器、每台两张卡画出这条路径。节点内是一个分组维度,跨节点的相同局部编号是另一个维度;A0 同时属于节点内组 {A0,A1} 和跨节点组 {A0,B0}。这些组允许重叠。

![跨节点与节点内分层拓扑及分块转发流程](https://pic.shaojiemike.top/shaojiemike/2026/09/efc8dbc0a960dcbff7be953e731156d7.png){ width=100% }
图 3:自绘分层路径与流程示意,只跟踪最终发给 B1 的一块 A0 数据。路径参考 DeepSeek-V3 v2 §3.2.2;图中的有界中转 buffer 和额度条件是通用分析模型,不是对报告未公开协议细节的补写。

这类方案有两种容易画混的顺序:图中是先跨机,再在目标机内转发;另一种是先在源机内按目标聚合,再跨机传输,必要时最后再分发。它们属于同一类分层思想,但不是一条相同的执行路径。画具体实现时,应按真实的数据搬运顺序选一种,而不是只画“机内快、机间慢”两个框。

它对阻塞的帮助来自两方面:把一些分发工作交给机内链路,以及让跨机接收与机内转发按 chunk 流水推进,减少“整批收完才开始转发”的等待。但分层没有自动规定 B0 最多接收几个外部发送者。如果许多源节点都向 B0 注入,而 B0 转发不及时,中转 buffer 一样会积压;跨机接收和机内转发两级都需要容量与进度约束。未到额度的数据留在上游,已就绪的块尽早进入下游,才形成受控流水。

拓扑分层还有一个常见反例:若所有跨机数据都压到一张代理 GPU 或一张网卡,原本可并行的带宽可能闲置,代理却成为瓶颈。分组映射应利用实际链路,不能只追求 rank 编号整齐。

现在可以把几个 group 放到同一张表里比较:

group 的含义 改变的对象 主要收益条件 新成本或边界
独立通信域 参与者与数据交换范围 应用允许通信局部化 全局语义可能改变,跨域交换需另行补齐
调度分组、在途窗口 当前允许推进的 peer 或请求 全开并发引发资源压力 更多轮次;窗口过小会吃不满带宽
拓扑分层 传输路径、聚合与复制位置 能利用快链路、分散热点或去除重复跨域传输 打包、转发、额外存储与代理热点
NCCL 调用 group 一批提交和共同推进的调用 收发有并发推进要求,或启动开销显著 必须满足消息匹配、提交与完成依赖

拓扑图和流程图分别画什么

拓扑图回答“谁能向谁发送、共享什么资源”;流程图回答“什么时候允许发、完成后谁被放行”。只画静态的四个 group 和箭头,还无法证明接收端受到保护。

  1. 先画物理边界,再叠逻辑分组。标出节点、网卡或交换域,把 rank 放回实际位置。逻辑 group 可以跨节点或互相重叠;只有确认存在独立物理链路,才把两组画成独立 rail。
  2. 一幅拓扑只高亮一个阶段。注明组成员、发送方向、箭头代表单 rank 流还是一束组间流;再数每个接收 rank 和共享网卡的入向。数据去往不同 rank,不代表没有竞争同一网卡。
  3. 流程图画出放行条件。把“等待 credit → 预留资源 → 提交匹配通信 → 数据可见 → 消费/完成转发 → 安全释放 → 返还 credit”按所分析协议展开。省略的步骤要说明;不要用一个含糊的“完成”同时代指源 buffer 可复用和目标 buffer 可复用。
  4. 在等待处标出数据存放位置。无许可时是源 buffer 保留数据,还是已经占用了中转 buffer?等待的工作是否挡住其他 peer 的进度?能否继续处理其他就绪块?这些信息才能区分受控背压与队头阻塞。

判断“避免了什么阻塞”时,最可靠的是给出能检查的约束:某阶段每个目标最多 s 个源、某接收 buffer 最多 C 个未释放块、某块就绪后不必等其他目标的数据。单纯写“分组避免阻塞”,无法区分这三种机制,也无法发现不正确的跨轮推进。

怎样判断自己的分组有没有用

分组不能突破必要数据量与物理带宽决定的下界。在没有拥塞额外损失的理想系统里,如果一个接收端必须收到 64 MB、有效接收带宽为 16 GB/s,那么仅搬这些字节就至少需要约 4 ms;两者都按十进制计。分组改变不了这个算术,只可能避免实际时间被额外排队和控制开销进一步拉长,也可能因调度空隙而变慢。

对稠密的 64-rank 交换,如果每轮每个 rank 最多与四个非自身目的 rank 发送,完整覆盖 63 个目的 rank 至少需要 16 轮,最后一轮是三个。相比全开,这种安排降低同时活跃的逻辑传输数量,总计 64 × 63 条有向传输仍然存在。这只是均匀调度模型;真实 MoE 可能稀疏、变长,不能把轮数比例当成延迟或显存比例。

实际验证可以围绕以下步骤展开:

  1. 固定语义和工作量。固定硬件、拓扑、rank 映射、数据类型、发送量与实现版本。MoE 重放同一批路由记录,检查每个目标得到的数据和反向结果一致;只调度分组时,不能靠少发 token 获得“加速”。
  2. 分开并发与错峰。比较全开、同一并发窗口但目的地顺序相同、同一窗口且目的地错开;再单独评估 credit 或滑动窗口。这样才能区分减少请求数和消除接收热点的贡献。
  3. 扫描适当的窗口范围。例如 1、2、4、8、16 和全开,分别测试小消息、大消息、均匀流量与热点流量。窗口应足够填满传输管线,又不把有限资源推入低效区间;最优值需要测量。
  4. 测设备完成与整体关键路径。预热后测重复迭代,统计每轮最慢 rank 的完成时间,再对这些迭代求中位数和 P95/P99;同时记录吞吐和完整训练或推理步骤时间。仅测异步 API 的主机返回耗时会漏掉真正通信。
  5. 找对应的原因证据。结合硬件可用计数器看链路利用率、队列压力、ECN/PFC 或重传、发送提交开销、credit 等待、同步等待及计算重叠。PFC 只适用于相应网络,不能要求 NVLink 或其他互联也出现该指标。

Open MPI 5.0.7 的 Alltoall 提供了一个现成的实验入口:在确认 tuned 组件实际被选中后,用 coll_tuned_use_dynamic_rules=1、coll_tuned_alltoall_algorithm=4 选择 linear_sync,再扫描 coll_tuned_alltoall_max_requests。这些设置针对该版本对应算法,不能直接当作 NCCL、Alltoallv 或 MoonEP 的通用参数。Open MPI 调优文档

如果窗口减小后,队列压力和尾延迟下降、有效吞吐提高,才有依据认为原来的过量并发伤害了性能。如果吞吐下降而拥塞指标没有改善,应考虑原来并没有这个瓶颈。如果只有某个 rank 始终很慢,则还要检查路由倾斜、计算延迟和硬件异常;平均分组不会减少那个 rank 实际必须处理的数据。

回到最初的问题:all-to-all 分组的优势,是把请求的时间、目的地、数据路径与推进依赖组织成硬件能够高效执行的工作。它可以缓解拥塞、缩小部分等待的影响,也可以通过正确的 NCCL 调用分组避免特定死锁。但“分 group 就能防止通信阻塞”仍然太宽泛。判断一个实现时,最有用的追问是:它限制了哪项资源,哪条等待关系因此改变,代价又落在哪里?

参考资料

Author

Shaojie Tan

Posted on

2026-09-13

Updated on

2026-10-28

Licensed under